package com.xiao.framework.ds.autoconfigure;

import static com.xiao.framework.ds.autoconfigure.bean.MultiDataSourceProperties.MULTI_PROPERTIES_PREFIX;

import com.xiao.framework.ds.autoconfigure.aspectj.MultiDataSourceInterceptor;
import com.xiao.framework.ds.autoconfigure.aspectj.MultiDataSourcePointcutAdvisor;
import com.xiao.framework.ds.autoconfigure.bean.MultiDataSourceProperties;
import com.xiao.framework.ds.autoconfigure.builder.DataSourceBuilder;
import com.xiao.framework.ds.autoconfigure.builder.HikariDataSourceBuilder;
import com.xiao.framework.ds.autoconfigure.factory.DefaultMultiDataSourceFactory;
import com.xiao.framework.ds.autoconfigure.factory.MultiDataSourceFactory;
import com.xiao.framework.ds.autoconfigure.pluing.MybatisReadWriteAutoRoutingPlugin;
import com.xiao.framework.ds.autoconfigure.strategy.LoadBalanceDataSourceStrategy;
import com.xiao.framework.ds.autoconfigure.strategy.MultiDataSourceDetermineStrategy;
import javax.sql.DataSource;
import org.apache.ibatis.binding.MapperProxy;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration.Hikari;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.Ordered;

/**
 *
 * 该自动配置类 必须在 {@link DataSourceAutoConfiguration} 自动配置之前 配置,提前将该类中的 MultiRoutingDataSource bean提前注入到ioc中
 * 用于阻止${@link DataSourceAutoConfiguration.PooledDataSourceConfiguration}初始化
 *
 * 因为 PooledDataSourceConfiguration类上有 @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })注解, 当ioc容器中不存在DataSource时初始化该配置类
 * 如果PooledDataSourceConfiguration类初始化了, 被@Import的类 DataSourceConfiguration.Hikari.class 也会被初始化 (该类头上也有@ConditionalOnMissingBean(DataSource.class))
 * {@link Hikari#dataSource(org.springframework.boot.autoconfigure.jdbc.DataSourceProperties)} bean 会注入到上下文,方法里会调用createDataSource(properties, HikariDataSource.class);
 * 创建默认数据源 这个时候 properties 里面很多属性都是默认null,因此会异常,导致上下文初始化失败
 *
 * @author <a href="mailto:xiaoquanidea@163.com">hutiquan</a>
 * @since 2019-08-24 17:52
 */
@SuppressWarnings("all")
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)// true--使用CGLIB基于类创建代理，false--使用java接口创建代理
@EnableConfigurationProperties(MultiDataSourceProperties.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
public class MultiDataSourceAutoConfiguration implements Ordered {

  @Bean
  @ConditionalOnMissingBean
  public MultiDataSourceDetermineStrategy multiDataSourceDetermineStrategy(){
    return new LoadBalanceDataSourceStrategy();
  }

  @Bean
  @ConditionalOnMissingBean
  public DataSourceBuilder<? extends DataSource> dataSourceBuilder(){
    return new HikariDataSourceBuilder();
  }

  @Bean
  @ConditionalOnMissingBean
  public MultiDataSourceFactory dataSourceFactory(MultiDataSourceProperties properties,DataSourceBuilder<? extends DataSource> dataSourceBuilder){
    if (properties.getBanner()) {
      new MultiDatasourceBanner().printBanner();
    }
    return new DefaultMultiDataSourceFactory(properties, dataSourceBuilder);
  }

  @Bean
  @ConditionalOnMissingBean
  public DataSource routingDataSource(MultiDataSourceDetermineStrategy strategy,MultiDataSourceFactory factory,MultiDataSourceProperties properties){
    MultiRoutingDataSource dataSource = new MultiRoutingDataSource(factory);
    dataSource.setPrimary(properties.getPrimary());
    dataSource.setMultiDataSourceDetermineStrategy(strategy);
    return dataSource;
  }

  @Bean
  @ConditionalOnMissingBean
  public MultiDataSourceInterceptor multiDataSourceInterceptor(){
    return new MultiDataSourceInterceptor();
  }

  @Bean
  @ConditionalOnMissingBean
  public MultiDataSourcePointcutAdvisor multiDataSourcePointcutAdvisor(MultiDataSourceInterceptor interceptor){
    MultiDataSourcePointcutAdvisor pointcutAdvisor = new MultiDataSourcePointcutAdvisor(interceptor);
    pointcutAdvisor.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return pointcutAdvisor;
  }

  @Bean
  @ConditionalOnClass(MapperProxy.class)
  @ConditionalOnProperty(
      prefix = MULTI_PROPERTIES_PREFIX ,
      name = "mybatis-read-write-auto-routing",
      havingValue = "true",
      matchIfMissing = false
  )
  public MybatisReadWriteAutoRoutingPlugin mybatisReadWriteAutoRoutingPlugin(){
    return new MybatisReadWriteAutoRoutingPlugin();
  }

  @Override
  public int getOrder() {
    return LOWEST_PRECEDENCE;
  }

}
